/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
class NBodyConst {
    public static final int COMMON_NUMBER_FOUR = 4;
    public static final int CURRENT_LOOP_STEP = 2;
    public static final int CURRENT_LOOP_COUNT = 24;
    public static final int CURRENT_LOOP_OPERATE_NUMBER = 100;
    public static final double FUNCTION_ADVANCE_ARGUMENT_DT = 0.01;
    public static final double FUNCTION_ENERGY_OPERATE_COEFFICIENT = 0.5;
    public static final int MAX_LOOP_COUNT = 8;
    public static final int MS_CONVERSION_RATIO = 1000;

    public static final double INSTANCE_BODY_ONE_X = 4.8414314424647209;
    public static final double INSTANCE_BODY_ONE_Y = -1.16032004402742839;
    public static final double INSTANCE_BODY_ONE_Z = -1.03622044471123109e-1;
    public static final double INSTANCE_BODY_ONE_VX = 1.66007664274403694e-3;
    public static final double INSTANCE_BODY_ONE_VY = 7.69901118419740425e-3;
    public static final double INSTANCE_BODY_ONE_VZ = -6.90460016972063023e-5;
    public static final double INSTANCE_BODY_ONE_MASS = 9.54791938424326609e-4;

    public static final double INSTANCE_BODY_TWO_X = 8.34336671824457987;
    public static final double INSTANCE_BODY_TWO_Y = 4.12479856412430479;
    public static final double INSTANCE_BODY_TWO_Z = -4.03523417114321381e-1;
    public static final double INSTANCE_BODY_TWO_VX = -2.76742510726862411e-3;
    public static final double INSTANCE_BODY_TWO_VY = 4.99852801234917238e-3;
    public static final double INSTANCE_BODY_TWO_VZ = 2.30417297573763929e-5;
    public static final double INSTANCE_BODY_TWO_MASS = 2.85885980666130812e-4;

    public static final double INSTANCE_BODY_THREE_X = 1.2894369562139131e1;
    public static final double INSTANCE_BODY_THREE_Y = -1.51111514016986312e1;
    public static final double INSTANCE_BODY_THREE_Z = -2.23307578892655734e-1;
    public static final double INSTANCE_BODY_THREE_VX = 2.96460137564761618e-3;
    public static final double INSTANCE_BODY_THREE_VY = 2.3784717395948095e-3;
    public static final double INSTANCE_BODY_THREE_VZ = -2.96589568540237556e-5;
    public static final double INSTANCE_BODY_THREE_MASS = 4.36624404335156298e-5;

    public static final double INSTANCE_BODY_FOUR_X = 1.53796971148509165e1;
    public static final double INSTANCE_BODY_FOUR_Y = -2.59193146099879641e1;
    public static final double INSTANCE_BODY_FOUR_Z = 1.79258772950371181e-1;
    public static final double INSTANCE_BODY_FOUR_VX = 2.68067772490389322e-3;
    public static final double INSTANCE_BODY_FOUR_VY = 1.62824170038242295e-3;
    public static final double INSTANCE_BODY_FOUR_VZ = -9.5159225451971587e-5;
    public static final double INSTANCE_BODY_FOUR_MASS = 5.15138902046611451e-5;

    public static final double PI = 3.141592653589793;
    public static final double SOLAR_MASS = COMMON_NUMBER_FOUR * PI * PI;
    public static final double DAYS_PER_YEAR = 365.24;
}

class Body {
    public double x;
    public double y;
    public double z;
    public double vx;
    public double vy;
    public double vz;
    public double mass;

    Body(double x1, double y1, double z1, double vx, double vy, double vz, double mass) {
        this.x = x1;
        this.vx = vx;
        this.y = y1;
        this.vy = vy;
        this.z = z1;
        this.vz = vz;
        this.mass = mass;
    }

    public Body offsetMomentum(double px, double py, double pz) {
        this.vx = -px / NBodyConst.SOLAR_MASS;
        this.vy = -py / NBodyConst.SOLAR_MASS;
        this.vz = -pz / NBodyConst.SOLAR_MASS;
        return this;
    }
}

class GlobalFunction {
    public static Body jupiter() {
        return new Body(NBodyConst.INSTANCE_BODY_ONE_X,
                NBodyConst.INSTANCE_BODY_ONE_Y,
                NBodyConst.INSTANCE_BODY_ONE_Z,
                NBodyConst.INSTANCE_BODY_ONE_VX * NBodyConst.DAYS_PER_YEAR,
                NBodyConst.INSTANCE_BODY_ONE_VY * NBodyConst.DAYS_PER_YEAR,
                NBodyConst.INSTANCE_BODY_ONE_VZ * NBodyConst.DAYS_PER_YEAR,
                NBodyConst.INSTANCE_BODY_ONE_MASS * NBodyConst.SOLAR_MASS);
    }

    public static Body saturn() {
        return new Body(NBodyConst.INSTANCE_BODY_TWO_X,
                NBodyConst.INSTANCE_BODY_TWO_Y,
                NBodyConst.INSTANCE_BODY_TWO_Z,
                NBodyConst.INSTANCE_BODY_TWO_VX * NBodyConst.DAYS_PER_YEAR,
                NBodyConst.INSTANCE_BODY_TWO_VY * NBodyConst.DAYS_PER_YEAR,
                NBodyConst.INSTANCE_BODY_TWO_VZ * NBodyConst.DAYS_PER_YEAR,
                NBodyConst.INSTANCE_BODY_TWO_MASS * NBodyConst.SOLAR_MASS);
    }

    public static Body uranus() {
        return new Body(NBodyConst.INSTANCE_BODY_THREE_X,
                NBodyConst.INSTANCE_BODY_THREE_Y,
                NBodyConst.INSTANCE_BODY_THREE_Z,
                NBodyConst.INSTANCE_BODY_THREE_VX * NBodyConst.DAYS_PER_YEAR,
                NBodyConst.INSTANCE_BODY_THREE_VY * NBodyConst.DAYS_PER_YEAR,
                NBodyConst.INSTANCE_BODY_THREE_VZ * NBodyConst.DAYS_PER_YEAR,
                NBodyConst.INSTANCE_BODY_THREE_MASS * NBodyConst.SOLAR_MASS);
    }

    public static Body neptune() {
        return new Body(NBodyConst.INSTANCE_BODY_FOUR_X,
                NBodyConst.INSTANCE_BODY_FOUR_Y,
                NBodyConst.INSTANCE_BODY_FOUR_Z,
                NBodyConst.INSTANCE_BODY_FOUR_VX * NBodyConst.DAYS_PER_YEAR,
                NBodyConst.INSTANCE_BODY_FOUR_VY * NBodyConst.DAYS_PER_YEAR,
                NBodyConst.INSTANCE_BODY_FOUR_VZ * NBodyConst.DAYS_PER_YEAR,
                NBodyConst.INSTANCE_BODY_FOUR_MASS * NBodyConst.SOLAR_MASS);
    }

    public static Body sun() {
        return new Body(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, NBodyConst.SOLAR_MASS);
    }
}

class NBodySystem {
	public Body[] bodies;

    NBodySystem(Body[] bodies) {
        this.bodies = bodies;
        double px = 0.0;
        double py = 0.0;
        double pz = 0.0;
        int size = bodies.length;
        for (int i = 0; i < size; i++) {
            Body b = bodies[i];
            double m = b.mass;
            px += b.vx * m;
            py += b.vy * m;
            pz += b.vz * m;
        }

        this.bodies[0].offsetMomentum(px, py, pz);
    }

    public void advance(double dt) {
        double dx;
        double dy;
        double dz;
        double distance;
        double mag;
        int size = bodies.length;

        for (int i = 0; i < size; i++) {
            Body bodyI = bodies[i];
            for (int j = i + 1; j < size; j++) {
                Body bodyJ = bodies[j];
                dx = bodyI.x - bodyJ.x;
                dy = bodyI.y - bodyJ.y;
                dz = bodyI.z - bodyJ.z;

                distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
                mag = dt / (distance * distance * distance);

                bodyI.vx -= dx * bodyJ.mass * mag;
                bodyI.vy -= dy * bodyJ.mass * mag;
                bodyI.vz -= dz * bodyJ.mass * mag;

                bodyJ.vx += dx * bodyI.mass * mag;
                bodyJ.vy += dy * bodyI.mass * mag;
                bodyJ.vz += dz * bodyI.mass * mag;
            }
        }

        for (int i = 0; i < size; i++) {
            Body body = bodies[i];
            body.x += dt * body.vx;
            body.y += dt * body.vy;
            body.z += dt * body.vz;
        }
    }

    public double energy() {
        double dx;
        double dy;
        double dz;
        double distance;
        double e = 0.0;
        int size = bodies.length;

        for (int i = 0; i < size; i++) {
            Body bodyI = bodies[i];

            e += NBodyConst.FUNCTION_ENERGY_OPERATE_COEFFICIENT * bodyI.mass
                    * (bodyI.vx * bodyI.vx + bodyI.vy * bodyI.vy + bodyI.vz * bodyI.vz);

            for (int j = i + 1; j < size; j++) {
                Body bodyJ = bodies[j];
                dx = bodyI.x - bodyJ.x;
                dy = bodyI.y - bodyJ.y;
                dz = bodyI.z - bodyJ.z;
                distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
                e -= (bodyI.mass * bodyJ.mass) / distance;
            }
        }
        return e;
    }
}

interface ArkTools {
    long timeInUs(int args);
}

/*
 * @State
 * @Tags Jetstream2
 */
class Benchmark {

    public static void main(String[] args) {
        Benchmark benchMark = new Benchmark();
        benchMark.runIterationTime();
    }
    
    /*
     * @Benchmark
     */
    public void run() {
        Double ret = 0.0;
        int n = 3;
        // System.out.println("Before the loop starts, ret's value is ->" + ret);

        while (n <= NBodyConst.CURRENT_LOOP_COUNT) {
            Body[] planets = new Body[] { GlobalFunction.sun(), GlobalFunction.jupiter(), GlobalFunction.saturn(),
                GlobalFunction.uranus(), GlobalFunction.neptune() };
            NBodySystem bodies = new NBodySystem(planets);
            int max = n * NBodyConst.CURRENT_LOOP_OPERATE_NUMBER;
            ret += bodies.energy();

            for (int i = 0; i < max; i++) {
                bodies.advance(NBodyConst.FUNCTION_ADVANCE_ARGUMENT_DT);
            }
            ret += bodies.energy();
            n *= NBodyConst.CURRENT_LOOP_STEP;
        }

        // System.out.println("After the loop ends, a's value is ->" + ret);

        double expected = -1.3524862408537381;
        if (ret != expected) {
            System.out.println("ERROR: bad result: expected =" + expected + "but got =" + ret);
        }
    }

    public void runIterationTime() {
        long start = System.nanoTime();
        for (int i = 0; i < NBodyConst.MAX_LOOP_COUNT; i++) {
            this.run();
        }
        long end = System.nanoTime();
        System.out.println("n-body: ms = " + (double)(end - start)/1000000);
    }
}
